ENZYME_BASIC
Overview
The ENZYME_BASIC function fits a variety of enzyme kinetics and saturation curve models to experimental data using nonlinear least squares regression. These models are fundamental in biochemistry for characterizing enzyme behavior, receptor-ligand binding, and saturation phenomena across many scientific disciplines.
The primary model supported is Michaelis-Menten enzyme kinetics, which describes the rate of enzymatic reactions as a function of substrate concentration. Proposed by Leonor Michaelis and Maud Menten in 1913, this model assumes that an enzyme (E) binds to a substrate (A) to form an enzyme-substrate complex (EA), which then releases product (P). The classical Michaelis-Menten equation is:
v = \frac{V_{max} \cdot [S]}{K_m + [S]}
where v is the reaction velocity, V_{max} is the maximum reaction rate at saturating substrate concentration, [S] is the substrate concentration, and K_m (the Michaelis constant) is the substrate concentration at which the reaction rate is half of V_{max}.
The function supports nine model variants: Michaelis Menten Enzyme Kinetics, Rectangular Hyperbola Saturation, Modified Hyperbola Normalized, Lineweaver Burk Reciprocal (linearized form), Reciprocal Normalized Intercept, Reciprocal Shifted Inverse, Reciprocal Scaled Saturation, Dual Michaelis Menten Systems (for enzymes with two binding sites), and Double Hyperbola Linear Composite (combining multiple saturation components).
This implementation uses scipy.optimize.curve_fit from SciPy, which employs the Levenberg-Marquardt algorithm for nonlinear least squares optimization. The function returns fitted parameter values along with standard errors derived from the covariance matrix, enabling assessment of parameter uncertainty. For additional background, see the SciPy GitHub repository.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=ENZYME_BASIC(xdata, ydata, enzyme_basic_model)
xdata(list[list], required): The xdata valueydata(list[list], required): The ydata valueenzyme_basic_model(str, required): The enzyme_basic_model value
Returns (list[list]): 2D list [param_names, fitted_values, std_errors], or error string.
Examples
Example 1: Demo case 1
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| michaelis_menten_enzyme_kinetics | 0.1 | 0.06498008539702818 |
| 1.3250000000000002 | 0.5683446788689119 | |
| 2.5500000000000003 | 0.9377997088396869 | |
| 3.7750000000000004 | 1.211858535427366 | |
| 5 | 1.3626993822182172 |
Excel formula:
=ENZYME_BASIC("michaelis_menten_enzyme_kinetics", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.06498008539702818;0.5683446788689119;0.9377997088396869;1.211858535427366;1.3626993822182172})
Expected output:
"non-error"
Example 2: Demo case 2
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| rectangular_hyperbola_saturation | 0.1 | 0.11932706591090628 |
| 1.3250000000000002 | 1.0436875011956384 | |
| 2.5500000000000003 | 1.722141283505607 | |
| 3.7750000000000004 | 2.225412946875709 | |
| 5 | 2.5024115928007262 |
Excel formula:
=ENZYME_BASIC("rectangular_hyperbola_saturation", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.11932706591090628;1.0436875011956384;1.722141283505607;2.225412946875709;2.5024115928007262})
Expected output:
"non-error"
Example 3: Demo case 3
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| modified_hyperbola_normalized | 0.1 | 0.019330267301049663 |
| 1.3250000000000002 | 0.11248020537854551 | |
| 2.5500000000000003 | 0.14397182574007733 | |
| 3.7750000000000004 | 0.16062304542853886 | |
| 5 | 0.16439025603643972 |
Excel formula:
=ENZYME_BASIC("modified_hyperbola_normalized", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.019330267301049663;0.11248020537854551;0.14397182574007733;0.16062304542853886;0.16439025603643972})
Expected output:
"non-error"
Example 4: Demo case 4
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| lineweaver_burk_reciprocal | 0.1 | 0.3522582190003078 |
| 1.3250000000000002 | 0.2409175159322812 | |
| 2.5500000000000003 | 0.18684894415663086 | |
| 3.7750000000000004 | 0.15506674362119408 | |
| 5 | 0.12405930160728645 |
Excel formula:
=ENZYME_BASIC("lineweaver_burk_reciprocal", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.3522582190003078;0.2409175159322812;0.18684894415663086;0.15506674362119408;0.12405930160728645})
Expected output:
"non-error"
Example 5: Demo case 5
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| reciprocal_normalized_intercept | 0.1 | 0.7909932916133989 |
| 1.3250000000000002 | 0.21348389333017329 | |
| 2.5500000000000003 | 0.13351478714162163 | |
| 3.7750000000000004 | 0.10834476290747956 | |
| 5 | 0.06464783146054215 |
Excel formula:
=ENZYME_BASIC("reciprocal_normalized_intercept", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.7909932916133989;0.21348389333017329;0.13351478714162163;0.10834476290747956;0.06464783146054215})
Expected output:
"non-error"
Example 6: Demo case 6
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| reciprocal_shifted_inverse | 0.1 | 0.35284126052879805 |
| 1.3250000000000002 | 0.24485205931758455 | |
| 2.5500000000000003 | 0.19124028371953072 | |
| 3.7750000000000004 | 0.1592789483474682 | |
| 5 | 0.1281063874377733 |
Excel formula:
=ENZYME_BASIC("reciprocal_shifted_inverse", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.35284126052879805;0.24485205931758455;0.19124028371953072;0.1592789483474682;0.1281063874377733})
Expected output:
"non-error"
Example 7: Demo case 7
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| reciprocal_scaled_saturation | 0.1 | 2.5072261060512977 |
| 1.3250000000000002 | 1.1448658485041547 | |
| 2.5500000000000003 | 0.7719635949939035 | |
| 3.7750000000000004 | 0.6108590108755223 | |
| 5 | 0.43126094772332 |
Excel formula:
=ENZYME_BASIC("reciprocal_scaled_saturation", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.5072261060512977;1.1448658485041547;0.7719635949939035;0.6108590108755223;0.43126094772332})
Expected output:
"non-error"
Example 8: Demo case 8
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| dual_michaelis_menten_systems | 0.2 | 0.5823529411764705 |
| 1.3 | 1.9520634920634918 | |
| 2.4 | 2.475 | |
| 3.5 | 2.766227004512322 | |
| 4.6 | 2.955270655270655 |
Excel formula:
=ENZYME_BASIC("dual_michaelis_menten_systems", {0.2;1.3;2.4;3.5;4.6}, {0.5823529411764705;1.9520634920634918;2.475;2.766227004512322;2.955270655270655})
Expected output:
"non-error"
Example 9: Demo case 9
Inputs:
| enzyme_basic_model | xdata | ydata |
|---|---|---|
| double_hyperbola_linear_composite | 0.25 | 0.9719806763285025 |
| 1.5 | 3.296933962264151 | |
| 2.75 | 4.492711492209558 | |
| 4 | 5.380219780219781 | |
| 5.25 | 6.132003773076406 |
Excel formula:
=ENZYME_BASIC("double_hyperbola_linear_composite", {0.25;1.5;2.75;4;5.25}, {0.9719806763285025;3.296933962264151;4.492711492209558;5.380219780219781;6.132003773076406})
Expected output:
"non-error"
Python Code
import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math
def enzyme_basic(xdata, ydata, enzyme_basic_model):
"""
Fits enzyme_basic models to data using scipy.optimize.curve_fit. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html for details.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
This example function is provided as-is without any representation of accuracy.
Args:
xdata (list[list]): The xdata value
ydata (list[list]): The ydata value
enzyme_basic_model (str): The enzyme_basic_model value Valid options: Michaelis Menten Enzyme Kinetics, Rectangular Hyperbola Saturation, Modified Hyperbola Normalized, Lineweaver Burk Reciprocal, Reciprocal Normalized Intercept, Reciprocal Shifted Inverse, Reciprocal Scaled Saturation, Dual Michaelis Menten Systems, Double Hyperbola Linear Composite.
Returns:
list[list]: 2D list [param_names, fitted_values, std_errors], or error string.
"""
def _validate_data(xdata, ydata):
"""Validate and convert both xdata and ydata to numpy arrays."""
for name, arg in [("xdata", xdata), ("ydata", ydata)]:
if not isinstance(arg, list) or len(arg) < 2:
raise ValueError(f"{name}: must be a 2D list with at least two rows")
vals = []
for i, row in enumerate(arg):
if not isinstance(row, list) or len(row) == 0:
raise ValueError(f"{name} row {i}: must be a non-empty list")
try:
vals.append(float(row[0]))
except Exception:
raise ValueError(f"{name} row {i}: non-numeric value")
if name == "xdata":
x_arr = np.asarray(vals, dtype=np.float64)
else:
y_arr = np.asarray(vals, dtype=np.float64)
if x_arr.shape[0] != y_arr.shape[0]:
raise ValueError("xdata and ydata must have the same number of rows")
return x_arr, y_arr
# Model definitions dictionary
models = {
'michaelis_menten_enzyme_kinetics': {
'params': ['Vmax', 'Km'],
'model': lambda x, Vmax, Km: Vmax * x / (Km + x),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.median(xa))),
'bounds': (0.0, np.inf),
},
'rectangular_hyperbola_saturation': {
'params': ['P1', 'P2'],
'model': lambda x, P1, P2: (P1 * x) / (P2 + x),
'guess': lambda xa, ya: (float(max(ya)), 1.0),
},
'modified_hyperbola_normalized': {
'params': ['T1', 'T2'],
'model': lambda x, T1, T2: x / (T1 * x + T2),
'guess': lambda xa, ya: (1.0, 1.0),
},
'lineweaver_burk_reciprocal': {
'params': ['a', 'b'],
'model': lambda x, a, b: 1.0 / (a + b * x),
'guess': lambda xa, ya: (1.0, 0.1),
},
'reciprocal_normalized_intercept': {
'params': ['A'],
'model': lambda x, A: 1.0 / (1.0 + A * x),
'guess': lambda xa, ya: (0.1,),
},
'reciprocal_shifted_inverse': {
'params': ['A'],
'model': lambda x, A: 1.0 / (x + A),
'guess': lambda xa, ya: (float(np.min(xa)),),
},
'reciprocal_scaled_saturation': {
'params': ['a', 'b'],
'model': lambda x, a, b: a / (1.0 + b * x),
'guess': lambda xa, ya: (float(np.max(ya) if np.max(ya) else 1.0), 0.1),
},
'dual_michaelis_menten_systems': {
'params': ['Vm1', 'Km1', 'Vm2', 'Km2'],
'model': lambda x, Vm1, Km1, Vm2, Km2: (Vm1 * x / (Km1 + x)) + (Vm2 * x / (Km2 + x)),
'guess': lambda xa, ya: (
float(0.6 * np.max(ya) if ya.size else 1.0),
float(np.median(xa)),
float(0.4 * np.max(ya) if ya.size else 1.0),
float(np.percentile(xa, 75)),
),
'bounds': (0.0, np.inf),
},
'double_hyperbola_linear_composite': {
'params': ['P1', 'P2', 'P3', 'P4', 'P5'],
'model': lambda x, P1, P2, P3, P4, P5: (P1 * x) / (P2 + x) + (P3 * x) / (P4 + x) + P5 * x,
'guess': lambda xa, ya: (float(max(ya)) / 2, float(np.median(xa)), float(max(ya)) / 2, float(np.median(xa)), 0.0),
'bounds': (0.0, np.inf),
}
}
# Validate model parameter
if enzyme_basic_model not in models:
return f"Invalid model: {str(enzyme_basic_model)}. Valid models are: {', '.join(models.keys())}"
model_info = models[enzyme_basic_model]
# Validate and convert input data
try:
x_arr, y_arr = _validate_data(xdata, ydata)
except ValueError as e:
return f"Invalid input: {e}"
# Perform curve fitting
try:
p0 = model_info['guess'](x_arr, y_arr)
bounds = model_info.get('bounds', (-np.inf, np.inf))
if bounds == (-np.inf, np.inf):
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, maxfev=10000)
else:
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, bounds=bounds, maxfev=10000)
fitted_vals = [float(v) for v in popt]
for v in fitted_vals:
if math.isnan(v) or math.isinf(v):
return "Fitting produced invalid numeric values (NaN or inf)."
except ValueError as e:
return f"Initial guess error: {e}"
except Exception as e:
return f"curve_fit error: {e}"
# Calculate standard errors
std_errors = None
try:
if pcov is not None and np.isfinite(pcov).all():
std_errors = [float(v) for v in np.sqrt(np.diag(pcov))]
except Exception:
pass
return [model_info['params'], fitted_vals, std_errors] if std_errors else [model_info['params'], fitted_vals]